package interpreter.debugger;

import interpreter.Program;
import interpreter.VirtualMachine;
import interpreter.bytecodes.ByteCode;
import interpreter.bytecodes.DumpByteCode;
import interpreter.bytecodes.debuggerByteCodes.LineByteCode;
import interpreter.debugger.ui.UI;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Stack;
import java.util.Vector;

/**
 *
 * @author Kris Selbekk
 */
public class DebugVirtualMachine extends VirtualMachine {

    // Debug specific fields
    private boolean isBpSet, isStepIn;
    private Stack<FunctionEnvironmentRecord> environmentStack;
    private Vector<Integer> breakPoints;
    private Vector<String> source;
    private Program program;

    /**
     * Constructor
     * @param p the program to be run (debugged)
     */
    public DebugVirtualMachine( Program p ) {
        super( p );

        program = p;
        breakPoints = new Vector<Integer>();
        environmentStack = new Stack<FunctionEnvironmentRecord>();
        loadSource();
    }

    /*************************/
    /* Execution methods etc */
    /*************************/

    /**
     * Executes the program
     *
     * Instantiates stacks, program counters and sets the program as "running".
     * Then <code>executeProgram()</code> executes each byte code by itself.
     *
     * The only difference within this and the VirtualMachine is that jumps out
     * of the loop for 
     */
    public void executeProgram() {
        setRunning( true );

        // Starts the UI
        new UI(this);
    }

    /**
     * Execution of dVM goes here.
     */
    public void exec() {
        // Executes each byte code until
        while( isRunning() && !isBreakpoint() ) {
            int pcBackup = getPC();

            if( isBreakpoint() ) {
                setRunning( false );
                return;
            }

            ByteCode code = program.getCode(getPC());
            code.exec(this);

            if( code instanceof LineByteCode )
                setCurrentLine( (Integer) code.getArgs() );

            // Prints out representation if "dump" is set to true
            if( isDump() && !( code instanceof DumpByteCode) )
                System.out.println( code +"\n"+ getDump());

            // If the program counter was NOT modified by the byte codes, tick forward
            if( pcBackup == getPC() )
                setPC( getPC()+1 );

        }
    }

    /*******************************/
    /* Implementations of commands */
    /*******************************/

    public void stepInto() {
        isStepIn = true; // Stepping in
        while( isRunning() && isStepIn() && !isBreakpoint()) {
            program.getCode(getPC()).exec(this);
            setPC( getPC()+1 );
        }
    }

    /**
     * Continues on with the execution of ByteCodes until the current environment
     * stack is popped (that is, when the function is done).
     */
    public void stepOut() {
        int size = environmentStack.size();
        while( isRunning() && environmentStack.size() >= size && !isBreakpoint() ) {
            program.getCode(getPC()).exec(this);
            setPC( getPC()+1 );
        }
    }

    public void stepOver() {
        if( environmentStack.isEmpty() ) return;
        int size = environmentStack.size();
        int start = environmentStack.peek().getLineCurrent();

        while( isRunning() && !isBreakpoint() ) {
            try {
                program.getCode(getPC()).exec(this);
                setPC( getPC()+1 );
                if( environmentStack.size() == size && environmentStack.peek().getLineCurrent() != start ) break;
                if( environmentStack.size() == (start - 1)) break;
            } catch(ArrayIndexOutOfBoundsException aioob ) {
                break;
            }
        }
    }


    /* Setters and getters for debug info */
    public String getFunctionName() {
        if( environmentStack.isEmpty() ) return null;
        return environmentStack.peek().getFunctionName();
    }
    public void setFunctionName(String n ) {
        if( !environmentStack.isEmpty() ) environmentStack.peek().setFunctionName( n );
        else { beginScope(); environmentStack.peek().setFunctionName( n ); }
    }

    public int getCurrentLine() {
        if( environmentStack.isEmpty() ) return 0;
        return environmentStack.peek().getLineCurrent();
    }
    public void setCurrentLine(int i) {
        if( !environmentStack.isEmpty() ) environmentStack.peek().setLineCurrent( i );
    }

    public int getStartLine() {
        if( environmentStack.isEmpty() ) return 0;
        return environmentStack.peek().getLineStart();
    }
    public void setStartLine(int i) {
        if( !environmentStack.isEmpty() ) environmentStack.peek().setLineStart( i );
    }

    public int getEndLine() {
        if( environmentStack.isEmpty() ) return 0;
        return environmentStack.peek().getLineEnd();
    }
    public void setEndLine(int i) {
        if( !environmentStack.isEmpty() ) environmentStack.peek().setLineEnd( i );
    }

    public void beginScope() {
        environmentStack.push( new FunctionEnvironmentRecord() );
    }
    public void endScope() {
        environmentStack.pop();
    }

    public void setBreakpoint( int n ) {
        if( !breakPoints.contains( n )) breakPoints.add(n);
    }

    public boolean isBreakpoint( int n ) {
        if( breakPoints.contains( n ) ) return true;
        return false;
    }

    public boolean isBreakpoint() {
        return isBreakpoint(getCurrentLine());
    }

    public boolean clearBreakpoint( int n ) {
        if( breakPoints.remove( new Integer(n) ) ) return true;
        return false;
    }

    public boolean clearBreakpoint() {
        return clearBreakpoint( getCurrentLine() );
    }

    public int clearBreakpoints() {
        int numBps = breakPoints.size();
        breakPoints.removeAllElements();
        return numBps;
    }

    public String listBreakpoints() {
        StringBuilder out = new StringBuilder();

        for( int line : breakPoints ) {
            out.append(line);
            out.append(", ");
        }

        if( out.length() > 2) out.setLength(out.length()-2);

        return out.toString();
    }

    public boolean isStepIn() {
        return isStepIn;
    }
    public void isStepIn( boolean step ) {
        isStepIn = step;
    }

    public int formalCount() {
        if( !environmentStack.isEmpty() )
            return environmentStack.peek().formalCount();
        return 0;
    }

    public int argCount() {
        return getStackSize();
    }

    public String getVariables() {
        if( !environmentStack.isEmpty() ) return environmentStack.peek().getArguments();
        return "";
    }

    public void enterSymbol(String name, int val) {
        if( !environmentStack.isEmpty() )
            environmentStack.peek().process("Enter " + name + " " + val);
    }

    public void popSymbol(int numSymbols ) {
        if( !environmentStack.isEmpty() ) {
            environmentStack.peek().process("Pop "+ numSymbols);
        }
    }

    public String getFilePath() { return program.getPath(); }

    
     /**
     * Get source code from file, place in Vector<String> object
     */
    private void loadSource() {

        try {
            BufferedReader srcFile = new BufferedReader( new FileReader( program.getPath() + ".x" ));
            source = new Vector<String>();

            for( int lineNo = 1; srcFile.ready(); lineNo++ ) {
                source.add(String.format("%3d. %s", lineNo, srcFile.readLine() ));
            }

        } catch( FileNotFoundException fnf ) {
            System.err.println("Error: Source file was not found.");
            System.exit(-1);
        } catch( IOException io ) {
            System.err.println("Error: IOException caught");
            System.exit(-1);
        }
    }

    /**
     * Prints the annotated source code
     */
    public void printAnnotatedSource() {

        // Preliminary printouts
        System.out.println("BP   |");
        System.out.println("------");

        // Loops through each line of source
        int lineNo = 1;
        for( String line : source ) {
            // If breakpoint is set, mark them
            if( isBreakpoint(lineNo)) line = "**" + line;
            else line = "  " + line;

            // Marks current line
            if( lineNo == getCurrentLine() ) line += "\t<-- Current line";

            // Prints the line itself and updates line number
            System.out.println(line);
            lineNo++;
        }
    }

    public String getSource( int i ) {
        if( source.size() < i || i <= 0 ) return null;
        return source.get(i-1).substring(5); // Strips annotations (is 5 correct?)
    }

    public int getSourceNumLines() {
        return source.size();
    }
    
}